﻿namespace AlgoHero.Modelo
{
    using System;
    using System.Collections;
    using System.Collections.Generic;    
    using System.Text;    

    public class MapaTemporal
    {
        #region Atributos

        private double tiempoTotal; /*Tiempo total de la canción (segundos). Utilizado en el constructor 
                                     *para calcular el tiempo donde empieza cada acorde.
                                     */

        private IList<NodoTemporal> listaNodosTemporales; 

        #endregion

        #region Constructor

        public MapaTemporal(Cancion unaCancion)
        {
            /* Crea una mapa temporal a partir de unaCancion, calculando el tiempo total que dura
             * la canción y creando un conjunto de nodos temporales que corresponden a la canción.
             */
            this.tiempoTotal = 0;
            this.listaNodosTemporales = new List<NodoTemporal>();

            foreach (Compas compas in unaCancion.Partitura.Compases)
            {
                this.AgregarAcordesComoNodos(compas, unaCancion.TiempoDeCompas, 
                                             unaCancion.Partitura.DuracionDeCompas, unaCancion.Mapeo);
            }
        }

        #endregion

        #region Métodos

        private IList<Tecla> ObtenerTeclasAPresionar(Acorde unAcorde, MapeoDeTeclas mapeoDeTeclas)
        {
            /* Devuelve las teclas que el jugador debe presionar para un acorde dado.
             * Lanza MapeoDeTeclasInvalidoException si no se puede acceder a las teclas correspondientes
             * a alguna nota.
             */

            IList<Tecla> teclasAPresionar = new List<Tecla>();
            
            foreach(Nota nota in unAcorde)
            {
                string nombreNota = nota.Nombre;

                try
                {
                    IList<Tecla> teclasDeNota = mapeoDeTeclas.GetTeclasPara(nombreNota);
                    
                    foreach(Tecla tecla in teclasDeNota)
                    {
                        if (!teclasAPresionar.Contains(tecla))
                        {
                            teclasAPresionar.Add(tecla.Copy());
                        }
                    }

                }
                catch(NotaInvalidaException)
                {
                    throw new MapeoDeTeclasInvalidoException();
                }
            }           

            return teclasAPresionar;            
        }                
        
        private void AgregarAcordesComoNodos(Compas unCompas, double tiempoDeCompas, int duracionDeCompas,
                                             MapeoDeTeclas mapeoDeTeclas)
        {
            /* Recorre los acordes de unCompas; para cada acorde, calcula las teclas a presionar, calcula
             * su duracion en segundos, y con ellos crea un NodoTemporal que es agregado al MapaTemporal.
             * El tiempoAcumulado de la Cancion es incrementado en la cantidad que dura el acorde, que será
             * el tiempoAcumulado del proximo NodoTemporal.
             */

            foreach (Acorde acorde in unCompas)
            {
                IList<Tecla> teclasAPresionar = ObtenerTeclasAPresionar(acorde, mapeoDeTeclas);
                double duracionDelAcorde = acorde.CalcularDuracionFisica(tiempoDeCompas, duracionDeCompas);

                NodoTemporal nodo = new NodoTemporal(acorde, this.tiempoTotal, duracionDelAcorde, teclasAPresionar);
                this.AddNodo(nodo);
                this.tiempoTotal += duracionDelAcorde; /*Al agregar el ultimo nodo, sera el tiempo total
                                                        *de la cancion.
                                                        */
            }
        }

        private void AddNodo(NodoTemporal unNodo)
        {            
            this.listaNodosTemporales.Add(unNodo);
        }

        public IList<Tecla> ObtenerTeclasAPresionar(double tiempoDeReproduccion)
        {
            /* Obtiene las teclas a presionar para un instante de reproduccion de la cancion.
             * Precondiciones: El tiempo debe ser no negativo y no mayor a la duracion total de la cancion.
             * Postcondiciones: Lanza ArgumentException si tiempo es negativo. Lanza TiempoInvalidoException 
             * si tiempoDeReproduccion es mayor que la duracion de la cancion.
             */

            NodoTemporal nodoCorrespondiente = ObtenerNodoCorrespondiente(tiempoDeReproduccion);

            return nodoCorrespondiente.Teclas;
        }

        public IList<Tecla> ObtenerTeclasAPresionar(double tiempoDeReproduccion, double amplitud)
        {
            /* Sobrecarga. Obtiene las teclas a presionar para un intervalo de la forma
             * tiempoDeReproduccion + amplitud. Devuelve null si no hay teclas.
             * Precondiciones: El tiempo debe ser no negativo y no mayor a la duracion total de la cancion.
             *                 La amplitud debe ser no negativo.
             * Postcondiciones: Lanza ArgumentException si tiempo o amplitud es negativo. Lanza TiempoInvalidoException 
             * si tiempoDeReproduccion es mayor que la duracion de la cancion.
             */

            if (tiempoDeReproduccion < 0 || amplitud < 0)
            {
                throw new ArgumentException("El tiempo de reproduccion debe ser no negativo.");
            }

            if (tiempoDeReproduccion > this.tiempoTotal)
            {
                throw new TiempoInvalidoException(
                                        "El tiempo de reproduccion es mayor que el tiempo total de la Cancion.");
            }

            IList<Tecla> teclasAPresionar;

            //Iteramos hasta obtener el nodo correspondiente
            foreach (NodoTemporal nodo in this.listaNodosTemporales)
            {
                if ((tiempoDeReproduccion >= nodo.TiempoDeInicio) &&
                    (tiempoDeReproduccion <= (nodo.TiempoDeInicio + amplitud)))
                {
                    teclasAPresionar = nodo.Teclas;
                    return teclasAPresionar;
                }
            }

            //Si no se encontro nodo, devolvemos null

            return null;
        }        
        
    
        private NodoTemporal ObtenerNodoCorrespondiente(double tiempoDeReproduccion)
        {
            /* Devuelve el nodo correspondiente con el tiempo de reproduccion de la cancion 
             * ('tiempoDeReproduccion'), o null si no lo encuentra. 
             */

            if (tiempoDeReproduccion < 0)
            {
                throw new ArgumentException("El tiempo de reproduccion debe ser no negativo.");
            }

            if (tiempoDeReproduccion > this.tiempoTotal)
            {
                throw new TiempoInvalidoException(
                                        "El tiempo de reproduccion es mayor que el tiempo total de la Cancion.");
            }                       

            //Iteramos hasta obtener el nodo correspondiente
            foreach (NodoTemporal nodo in this.listaNodosTemporales)
            {
                if (tiempoDeReproduccion >= nodo.TiempoDeInicio &&
                    tiempoDeReproduccion < (nodo.TiempoDeInicio + nodo.DuracionFisica))
                {
                    return nodo;
                }
            }

            return null;            
        }

        public Acorde ObtenerAcordePara(double tiempoDeReproduccion)
        {
            /* Devuelve el acorde correspondiente al tiempo de reproduccion dado.
             * Precondiciones: El tiempo debe ser no negativo y no mayor a la duracion total de la cancion.
             * Postcondiciones: Lanza ArgumentException si tiempo es negativo. Lanza TiempoInvalidoException 
             * si tiempoDeReproduccion es mayor que la duracion de la cancion.
             */

            NodoTemporal nodoCorrespondiente = ObtenerNodoCorrespondiente(tiempoDeReproduccion);

            return nodoCorrespondiente.Acorde;
        }

        public double ObtenerTiempoDeProxAcorde(double tiempoDeReproduccion)
        {
            foreach(NodoTemporal nodo in this.listaNodosTemporales)
            {
                if (nodo.TiempoDeInicio > tiempoDeReproduccion)
                {
                    return nodo.TiempoDeInicio;
                }                 
            }

            return -1;
        }

        public int DameCantidadDeTeclasPerdidas(double tiempoDeReproduccion)
        {
            int cantidadTeclasPerdidas = 0;

            foreach (NodoTemporal nodo in this.listaNodosTemporales)
            {
                if (nodo.TiempoDeInicio < tiempoDeReproduccion)
                {

                    foreach (Tecla tecla in nodo.Teclas)
                    {
                        if (!tecla.FueApretada)
                        {
                            cantidadTeclasPerdidas++;
                        }
                    }
                }                    
            }

            return cantidadTeclasPerdidas;
        }

                
        #endregion

        #region Propiedades

        public int Cantidad
        {
            get { return this.listaNodosTemporales.Count; }
        }

        public double TiempoTotal
        {
            get { return this.tiempoTotal; }
        }

        #endregion
 

    }
}
